feat(chrome): add native browser debugging tools - #7027
yiliang114 wants to merge 24 commits into
Conversation
🖼️ web-shell visual previewRendered against a mock daemon (no real backend): the PR base vs this PR head Screenshots · before / afterℹ️ No screenshot changed against the PR base — but this PR edits 2 render-shaping files:
Either the change has no visual effect (logic, plumbing, a state the scenarios never reach), or no scenario renders this UI — in which case the preview cannot see it, and an empty result is a coverage gap rather than a clean bill of health. To make it visible, add a scenario to Full-resolution recordings (.webm) are attached to the workflow run. — Qwen Code · web-shell visuals |
🩺 serve daemon A/BBuilt the PR base vs this PR head
|
| field | PR base (before) | this PR (after) |
|---|---|---|
features[] |
— | "allow_origin" |
features[] |
— | "client_mcp_over_ws" |
health-deep-with-session
| field | PR base (before) | this PR (after) |
|---|---|---|
activeWorkStaleMs |
5 |
6 |
— Qwen Code · serve A/B
…ative-tools # Conflicts: # docs/users/qwen-serve.md # packages/cli/src/serve/acp-http/index.ts
|
Thanks for the PR! Template looks good ✓ Problem: This addresses a real workflow gap — browser automation today requires users to separately install and configure an external Chrome DevTools MCP adapter. Bundling it into the npm package creates security-scanner and release-compliance friction. The proposed solution (native tools in the Chrome extension, which already holds browser permissions) is a genuine improvement to the user experience. Refs #5626. Direction: Aligned with Qwen Code's developer tooling mission. Moving browser tools into the extension eliminates an external dependency and simplifies setup. The pairing flow for daemon-extension trust is a reasonable security measure. No concerns about product direction. Size: This is a large cross-package feature — ~3,330 production logic lines across 39 source files, ~2,810 test lines across 20 test files, and ~340 doc/plan lines. Core path touches are minimal (2 lines in Approach: The scope is large but focused — all changes serve the stated goal of native browser tools + pairing + ACP integration. The implementation plan in 中文说明感谢贡献! 模板完整 ✓ 问题:这是一个真实的工作流缺口——目前浏览器自动化需要用户单独安装和配置外部 Chrome DevTools MCP adapter。将其打入 npm 主包会带来安全扫描和发布合规问题。提议的方案(将原生工具放在已持有浏览器权限的 Chrome 插件中)是对用户体验的真实改善。关联 #5626。 方向:与 Qwen Code 的开发者工具定位一致。将浏览器工具移入插件消除了外部依赖并简化了设置。daemon-extension 配对的信任流程是合理的安全措施。产品方向没有问题。 规模:这是一个大型跨包特性——约 3,330 行生产逻辑代码分布在 39 个源文件中,约 2,810 行测试代码分布在 20 个测试文件中,约 340 行文档/计划。核心路径触及极少( 方案:范围较大但聚焦——所有改动都服务于原生浏览器工具 + 配对 + ACP 集成的目标。 — Qwen Code · qwen3.7-max Reviewed at |
Code ReviewIndependent proposal before reading the diff: to add native browser debugging tools to the Chrome extension, I'd implement a The PR's approach matches and exceeds this. The implementation is well-layered:
No critical blockers or security issues found. The crypto is sound, error handling is thorough, timeouts are bounded, and the secret redaction covers all the paths I checked. Code follows project conventions — ESM, no One note: TestingUnit tests (826 tests, all passing): Real-scenario testing (tmux): Not applicable for this PR. The feature is a Chrome extension that requires a real Chrome browser, extension installation, and interactive daemon pairing. The PR author has explicitly noted: "A final interactive Chrome smoke pass is still required before marking the PR ready." This can only be verified by a reviewer on macOS with Chrome installed. 中文说明代码审查在阅读 diff 之前,我的独立方案是:基于 PR 的方案与我的思路一致且更为完善:
未发现关键阻塞问题或安全问题。密码学实现正确,错误处理完善,超时有界,脱敏覆盖了我检查的所有路径。代码遵循项目规范——ESM、无 测试单元测试(826 个测试,全部通过): 覆盖了 chrome-extension(53)、cli pairing 和 ACP 集成(302)、acp-bridge(471)。 真实场景测试(tmux): 不适用于此 PR。该功能是 Chrome 插件,需要真实 Chrome 浏览器、插件安装和交互式 daemon 配对。PR 作者已明确说明:"在标记 PR ready 之前,仍需完成一次真实 Chrome 交互冒烟测试。" 这只能由 macOS 上的 reviewer 安装 Chrome 后验证。 — Qwen Code · qwen3.7-max Reviewed at |
|
Confidence: 4/5 — Solid feature implementation with thorough test coverage and sound security design. The only gap is interactive Chrome testing, which requires a human reviewer with Chrome installed. This PR does exactly what it says: moves browser debugging tools from an external adapter into the Chrome extension, using The implementation is well-layered (debugger session → tools → MCP server → connection), the secret redaction is comprehensive across all text paths, and the test suite (826 tests across 12 files) covers the full stack from extension service worker through daemon pairing to ACP bridge integration. The only thing I can't verify in CI is the actual Chrome browser behavior — does the side panel pairing flow work smoothly, do the tools interact correctly with real web pages, does the debugger attachment/detachment handle edge cases. The PR author has explicitly flagged this: "A final interactive Chrome smoke pass is still required before marking the PR ready." That's honest and appropriate — a reviewer on macOS with Chrome can verify this. Approving. The code is clean, the tests are green, and the feature solves a real user problem. 中文说明置信度:4/5 — 实现扎实,测试覆盖充分,安全设计合理。唯一无法在 CI 中验证的是真实 Chrome 浏览器的交互行为,需要有 Chrome 的 reviewer 手动验证。 这个 PR 实现了它所描述的功能:将浏览器调试工具从外部 adapter 移入 Chrome 插件,使用 实现分层清晰(debugger session → tools → MCP server → connection),敏感信息脱敏覆盖了所有文本路径,测试套件(12 个文件中 826 个测试)覆盖了从插件 service worker 到 daemon 配对再到 ACP bridge 集成的完整链路。 唯一无法在 CI 中验证的是真实 Chrome 浏览器行为——侧边栏配对流程是否顺畅,工具是否与实际网页正确交互,debugger 连接/断开是否处理了边缘情况。PR 作者已明确标注了这一点:"在标记 PR ready 之前,仍需完成一次真实 Chrome 交互冒烟测试。" 这是诚实且恰当的——macOS 上有 Chrome 的 reviewer 可以验证。 批准。代码干净,测试通过,功能解决了真实的用户问题。 — Qwen Code · qwen3.7-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
…ative-tools # Conflicts: # docs/users/qwen-serve.md # integration-tests/cli/qwen-serve-routes.test.ts # package.json # packages/acp-bridge/src/bridge.test.ts # packages/acp-bridge/src/bridgeClient.test.ts # packages/acp-bridge/src/bridgeClient.ts # packages/chrome-extension/README.md # packages/chrome-extension/config/esbuild.background.config.js # packages/chrome-extension/docs/05-daemon-direct-architecture.md # packages/chrome-extension/package.json # packages/chrome-extension/public/sidepanel.js # packages/chrome-extension/scripts/artifact-scan.js # packages/chrome-extension/scripts/package-extension.js # packages/chrome-extension/scripts/sync-extension.js # packages/cli/src/serve/acp-http/client-mcp-sender-registry.test.ts # packages/cli/src/serve/acp-http/dispatch.ts # packages/cli/src/serve/acp-http/index.ts # packages/cli/src/serve/acp-http/transport.test.ts # packages/cli/src/serve/process-env-guard.test.ts # packages/cli/src/serve/routes/session.ts # packages/cli/src/serve/run-qwen-serve.test.ts # packages/cli/src/serve/run-qwen-serve.ts # packages/cli/src/serve/server.test.ts # packages/cli/src/serve/server.ts # packages/cli/src/serve/types.ts # packages/sdk-typescript/src/daemon/DaemonClient.ts # packages/sdk-typescript/src/daemon/DaemonSessionClient.ts # packages/sdk-typescript/test/unit/DaemonClient.test.ts # packages/web-shell/client/components/WorkspaceSessionProvider.test.tsx # packages/web-shell/client/components/WorkspaceSessionProvider.tsx # packages/web-shell/client/main.tsx # packages/webui/src/daemon/session/DaemonSessionProvider.test.tsx # packages/webui/src/daemon/session/DaemonSessionProvider.tsx # scripts/tests/package-scripts.test.js
The suite registers a sender with `reg.set(...)`, but the registry exposes
`claim(serverName, sender, owner)`; every other call site in this PR already
uses it. `tsc --build` fails the tree at head:
src/serve/acp-http/client-mcp-sender-registry.test.ts(117,9): error TS2339:
Property 'set' does not exist on type 'ClientMcpSenderRegistry'.
That failure aborts `npm run build --workspace=packages/cli`, so every CI lane
fails in `Install dependencies` before it reaches its own step. `claim` on a
fresh registry has the same effect the test expects from `set`.
|
Pushed
Verified locally on the new head: The 8 remaining errors are pre-existing on this base, present before the change and untouched by the PR: Heads up on CI: the PR is |
|
Merged Eight files conflicted, three of them with design weight:
Local verification on
|
`capabilities.ts` advertises `client_mcp_over_ws` after `session_worktree_reset_v1`; the integration expectation, written before the merge, still listed it ahead of `workspace_session_live_state`.
|
Pushed
Checked the whole expectation against the |
…tions The daemon pins the official Chrome extension origin and treats a configured CDP adapter command as an active tunnel, so browser_automation_mcp is advertised once QWEN_CDP_MCP_COMMAND is set and security.allowOriginMode reports 'specific'. Update the two assertions that still pinned the pre-extension defaults.
|
Follow-up on the merge commit: two capability assertions in Root cause. This PR makes the daemon extension-ready by default in two ways:
Change (
Both behaviour changes are deliberate in this PR and are covered by its own new tests; the two updated assertions were written before the extension defaults existed. Flagging them explicitly since Verification: |
…ures The warning banner stopped rendering for daemons without the legacy adapter feature: the snapshot branch returned early, before the capability model that attributes the chat-only and tunnel-only messages ran. Poll `/workspace/mcp` only where an adapter is discoverable and derive the banner from the feature list otherwise. Fixture and expectation updates for the extension pairing contract this branch introduces: - sidepanel fixtures store a paired credential, answer the `/extension/pairing/verify` challenge the way a paired daemon does, render the `#pair-form` / `.status__text` elements the panel dereferences, and stub `chrome.runtime.sendMessage`. - the CDP transport cases wire a pairing verifier, and the registered CDP MCP server now receives a `?access_token=` endpoint. - the serve env guard accounts for the CDP MCP adapter command read. - the web shell boot mock exports `getExtensionPairingCredential`.
|
The unit job on
|
….test.tsx main.tsx reads getExtensionPairingCredential() at startup, so every spec that mocks ./config/daemon has to expose it. main-boot.test.tsx already did; main.test.tsx did not, and vi.mock turned the missing export into an unhandled rejection that failed the workspace even though all 310 files and 7644 tests reported as passed: Error: [vitest] No "getExtensionPairingCredential" export is defined on the "./config/daemon" mock. ❯ main main.tsx:367:38 ❯ main.test.tsx:54:1 Add the same one-line stub the other spec uses.
|
Correction to my previous comment: the nonzero exit of The job reported the workspace, but the failure had no
vi.mock('./config/daemon', () => ({
getDaemonBaseUrl: () => '',
getDaemonToken: () => 'token',
+ getExtensionPairingCredential: () => undefined,
hasReloadSurvivableDaemonToken: () => testState.tokenSurvivesReload,Evidence so far:
|
|
Follow-up on the local web-shell noise I mentioned: it is pre-existing, not contention and not this branch. Running the same file on clean Identical counts on both trees, and CI on |
The three chrome-extension packaging cases added by this PR were written
against an earlier design that the same PR superseded, and they had never
run: the unit job aborted in the web-shell workspace before reaching
test:scripts. With that abort fixed they fail:
AssertionError: expected undefined to be defined
scripts/tests/chrome-extension-package.test.js:34
TypeError: (0 , scanZip) is not a function
AssertionError: expected '0.23.3.65535' to be '0.23.3'
Package-extension.js spawns the POSIX zip binary, artifact-scan.js
exports scanZipArtifact (returning { file, signature } findings), and
sync-extension.js writes the four-component Chrome version through
toChromeManifestVersion. Point the cases at those, and assert the
packaging wiring where it exists: the CI workflow step that runs
`npm -w packages/chrome-extension run package`.
|
The mock fix worked: the workspace phase is green on Those cases were written against an earlier design that this PR superseded, and nothing caught it because the unit job never reached
Local run: |
|
Both fixes hold on
|
What this PR does
This PR gives the official Qwen Code Chrome extension a native browser-debugging tool catalog implemented with
chrome.debugger. After a user startsqwen serveand completes first-use pairing, the agent can inspect the accessibility tree, navigate, click, fill forms, send keys, capture screenshots, evaluate JavaScript, read console output, and inspect network requests and opt-in response bodies.The extension registers these tools through the daemon's reverse MCP channel. The official extension origin and reverse channels work by default, while custom extension builds still require an explicit allowed origin. A process-scoped mutual-proof pairing flow prevents an unrelated local process from claiming the extension or Chrome sessions, and internal CDP compatibility endpoints use an unguessable process token.
The existing external adapter command remains as a compatibility path. Neither the extension ZIP nor the main Qwen Code npm package bundles
chrome-devtools-mcp, Puppeteer, or another external browser automation server. Release packaging rejects stale custom build output and scans the generated extension artifact.Why it's needed
The previous browser automation path depended on separately installing and configuring an external Chrome DevTools MCP adapter. Bundling that adapter into the main npm package also created security-scanner and release-compliance concerns. Hosting the baseline tools in the Chrome extension uses the browser permission the extension already owns, removes the extra installation step, and keeps the Node.js agent runtime in
qwen serve.Reviewer Test Plan
How to verify
qwen servewith no browser-related environment variables or origin flags. Confirm that the side panel discovers the daemon and asks for the pairing code printed in the terminal.Evidence (Before & After)
Before: browser debugging required a separately installed adapter and explicit environment configuration.
After: the official extension needs only
qwen serveplus first-use pairing; the extension-hosted catalog becomes available immediately after pairing.Automated evidence: 61 extension tests, extension typecheck/build/package/artifact scan, targeted CLI/ACP/SDK/Web UI/Web Shell tests, full workspace typecheck/lint/build, and a real npm tarball content scan all pass. The npm tarball contains 833 files and no Chrome extension source, extension ZIP, or external Chrome DevTools MCP implementation.
Tested on
Environment (optional)
Node.js 22, macOS, production extension build and local
qwen servebuild.Risk & Scope
debuggerpermission and controls the active HTTP(S) tab; Chrome allows only one debugger attachment per target, so an open DevTools session can conflict with automation.Linked Issues
Refs #5626
中文说明
本 PR 做了什么
这个 PR 为 Qwen Code 官方 Chrome 插件增加了基于
chrome.debugger实现的原生浏览器调试工具。用户启动qwen serve并完成首次配对后,Agent 可以读取无障碍树、导航页面、点击和填写表单、发送按键、截图、执行 JavaScript、读取 console 输出,以及检查网络请求和按需获取响应体。插件通过 daemon 的反向 MCP 通道注册这些工具。官方插件的 origin 和反向通道默认启用,自定义插件仍需显式配置允许的 origin。进程级双向证明配对流程可防止无关的本地进程冒充插件或接管 Chrome 会话,内部 CDP 兼容端点使用不可猜测的进程令牌。
现有外部 adapter 命令继续作为兼容路径保留。Chrome 插件 ZIP 和 Qwen Code npm 主包都不会打包
chrome-devtools-mcp、Puppeteer 或其他外部浏览器自动化服务。发布流程会拒绝陈旧的自定义构建目录,并扫描最终生成的插件产物。为什么需要
此前浏览器自动化需要用户单独安装并配置外部 Chrome DevTools MCP adapter。将该 adapter 打进 npm 主包也会带来安全扫描和发布合规风险。把基础工具放在 Chrome 插件内,可以直接复用插件已有的浏览器权限,去掉额外安装步骤,同时继续把依赖 Node.js 的 Agent runtime 保留在
qwen serve中。Reviewer 测试计划
如何验证
qwen serve。确认侧边栏能发现 daemon,并要求输入终端打印的配对码。前后对比证据
改动前:浏览器调试需要单独安装 adapter,并显式配置环境变量。
改动后:官方插件只需启动
qwen serve并完成首次配对,随后立即提供插件内置的工具目录。自动化验证:61 个插件测试、插件 typecheck/build/package/artifact scan、CLI/ACP/SDK/Web UI/Web Shell 定向测试、全 workspace typecheck/lint/build,以及真实 npm tarball 内容扫描均通过。npm tarball 包含 833 个文件,其中没有 Chrome 插件源码、插件 ZIP 或外部 Chrome DevTools MCP 实现。
测试平台
环境(可选)
Node.js 22、macOS、生产插件构建和本地
qwen serve构建。风险与范围
debugger权限,并控制当前 HTTP(S) tab;Chrome 对同一 target 只允许一个 debugger attachment,因此已打开的 DevTools 可能与自动化冲突。关联 Issue
Refs #5626